import java.util.ArrayList;
import java.util.List;


public class State {

	private Cell[][] _area; // graph
	private int _width; // width of the graph
	private int _height; // height of the graph
	private List<Cell> _dirtCells;
	private List<Cell> _obstacleCells; // List containing obstacles. Really only used when the graph is initialized.

	private int _dirtLeft;
	
	
	private Cell _home; // Home cell.
	private String[] _directions = {"NORTH", "EAST", "SOUTH", "WEST"};
	private int _dirIndex; // The index for which direction the agent is facing.
	private int[] _aPos; // Agent position
	private boolean _isOn; // Has the agent turned itself on?
	
	public State()
	{
		_dirtCells = new ArrayList<Cell>();
		_obstacleCells = new ArrayList<Cell>();
	}
	
	/* Copy constructor */
	public State(Cell[][] area, int width, int height, int dirtLeft, Cell home, int dirIndex, int[] agentPos, boolean isOn)
	{
		_area = area;
		_width = width;
		_height = height;
		_dirtLeft = dirtLeft;
		_dirIndex = dirIndex;
		_home = home;
		_aPos = agentPos;
		_isOn = isOn;
		
	}
	
	/*
	 * Initializes the graph with width and height. Only called for the initial state. 
	 */
	public void initArea(int width, int height)
	{
		_width = width;
		_height = height;
		_area = new Cell[width][height];
		_isOn = false;
		
		_area[_home.getX()][_home.getY()] = _home;
		_aPos = new int[] {_home.getX(), _home.getY()};
		
		for (Cell c : _dirtCells)
		{
			_area[c.getX()][c.getY()] = new Cell(false, c.getX(), c.getY(), c.getPercept());
		}
		
		for (Cell c : _obstacleCells)
		{
			_area[c.getX()][c.getY()] = new Cell(false, c.getX(), c.getY(), c.getPercept());
		}
		
		_dirtLeft = _dirtCells.size();
		_dirtCells.clear();
		_obstacleCells.clear();
		
		for (int x = 0; x < _width; ++x)
		{
			for (int y = 0; y < _height; ++y)
			{
				if (_area[x][y] == null)
				{
					_area[x][y] = new Cell(false, x, y, "");
				}
			}
		}
		
		System.out.println("Orientation: " + _directions[_dirIndex]);
		System.out.println("Location in grid: " + _aPos[0] + "," + _aPos[1]);
		System.out.println("Home location: " + _home.getX() + "," + _home.getY());
	}
	
	/* Goal function. If there are no dirty cells left, we have found a solution. */
	public boolean goalTest()
	{
		if (_dirtLeft == 0)
		{
			return true;
		}
		return false;
	}
	
	/* Returns a list of legal moves that the agent can execute in a state. */
	public List<String> legalMoves()
	{
		List<String> legal = new ArrayList<String>();
		int aX = _aPos[0], aY = _aPos[1];
		
		if (!_isOn) // The only legal move if true
		{
			legal.add("TURN_ON");
			return legal;
		}
		else if (_area[aX][aY].getPercept().equals("DIRT")) // The only legal move if true.
		{
			legal.add("SUCK");
			return legal;
		}
		else if (_directions[_dirIndex].equals("NORTH"))
		{
			if (aY+1 < _height && !_area[aX][aY+1].getPercept().equals("OBSTACLE"))
			{
				legal.add("GO");		
			}
		}
		else if (_directions[_dirIndex].equals("SOUTH"))
		{
			if (aY-1 >= 0 && !_area[aX][aY-1].getPercept().equals("OBSTACLE"))
			{
				legal.add("GO");			
			}
		}
		else if (_directions[_dirIndex].equals("EAST"))
		{
			if (aX+1 < _height && !_area[aX+1][aY].getPercept().equals("OBSTACLE"))
			{
				legal.add("GO");	
			}
		}
		else if (_directions[_dirIndex].equals("WEST"))
		{
			if (aX-1 >= 0 && !_area[aX-1][aY].getPercept().equals("OBSTACLE"))
			{
				legal.add("GO");	
			}
		}
		
		/* An agent is always able to turn if it has turned itself on and a cell doesn't contain
		 * dirt */
		legal.add("TURN_LEFT"); 
		legal.add("TURN_RIGHT");
		
		return legal;
	}
	
	/* Returns a successor state (state after an action has been executed. Used with a newly created node */
	public State getNextState(String action)
	{
		// Copy current state
		State next = new State(_area.clone(), _width, _height, _dirtLeft, _home, _dirIndex, _aPos, _isOn);
		if (action.equals("TURN_ON"))
		{
			next.turnOn();
		}
		else if (action.equals("GO"))
		{
			int[] modifier = getMoveDirection();
			int[] nextPos = {_aPos[0] + modifier[0], _aPos[1] + modifier[1]};
			next.setAgentPos(nextPos);
		}
		else if (action.equals("SUCK"))
		{
			next.removeDirt();
		}
		else if (action.equals("TURN_LEFT"))
		{
			next.turnLeft();
		}
		else if (action.equals("TURN_RIGHT"))
		{
			next.turnRight();
		}
		
		return next;
	}
	
	public boolean hasVisited(State child)
	{
		int[] cPos = child.getAgentPos();
		if (_dirtLeft == child.getDirtLeft() && 
			_aPos[0] == cPos[0] && _aPos[1] == cPos[1] && 
			child.getDirIndex() == _dirIndex &&
			_isOn == child.isOn())
		{
			return true;
		}
		return false;
	}
	
	/* Used by getNextState() to update the dirtCells list and graph, for a successor state. */
	public void removeDirt()
	{
		_dirtLeft--;
		_area[_aPos[0]][_aPos[1]].setPercept("");
		System.out.println("SUCK HAPPENED");
		System.out.println("Dirt left: " + _dirtLeft);
		printState();
		
	}
	
	/* Get which direction the agent is facing based on the direction index */
	public int[] getMoveDirection()
	{
		if (_directions[_dirIndex].equals("NORTH"))
		{
			return new int[] {0,1};
		}
		else if (_directions[_dirIndex].equals("SOUTH"))
		{
			return new int[] {0,-1};
		}
		else if (_directions[_dirIndex].equals("EAST"))
		{
			return new int[] {1,0};
		}
		else // facing west
		{
			return new int[] {-1,0};
		}
	}
	
	/* Called once, when the initial state is initialized. Sets the direction index. */
	public void initDirIndex(String orientation)
	{
		if (orientation.equals("NORTH"))
		{
			_dirIndex = 0;
		}
		else if (orientation.equals("WEST"))
		{
			_dirIndex = 1;
		}
		else if (orientation.equals("SOUTH"))
		{
			_dirIndex = 2;
		}
		else if (orientation.equals("EAST"))
		{
			_dirIndex = 3;
		}
	}
	
	/* Turns left. Used to update a successor state */
	public void turnLeft()
	{
		_dirIndex--;
		if (_dirIndex < 0)
		{
			_dirIndex = 3;
		}
	}

	/* Turns right. Used to update a successor state */
	public void turnRight()
	{
		_dirIndex++;
		if (_dirIndex > 3)
		{
			_dirIndex = 0;
		}
	}
	
	public Cell getHome() 
	{
		return _home;
	}

	public void setHome(int x, int y) 
	{
		this._home = new Cell(true, x, y, "");
	}

	public void addDirt(int x, int y)
	{
		_dirtCells.add(new Cell(false, x, y, "DIRT"));	
	}
	
	public int getDirtLeft()
	{
		return _dirtLeft;
	}
	
	public void addObstacle(int x, int y)
	{
		_obstacleCells.add(new Cell(false, x, y, "OBSTACLE"));
	}
	
	public void setArea(Cell[][] area)
	{
		_area = area;
	}
	
	public void setAgentPos(int[] pos)
	{
		_aPos = pos;
	}
	
	public int[] getAgentPos()
	{
		return _aPos;
	}
	
	public int getDirIndex()
	{
		return _dirIndex;
	}
	
	public void turnOn()
	{
		_isOn = true;
	}
	
	public boolean isOn()
	{
		return _isOn;
	}
	
	
	/* [Debug] Prints the graph in its current state. 
	 * V = player
	 * O = empty cell
	 * D = dirt
	 * X = obstacle
	 */
	public void printState()
	{
		for (int y = _height-1; y >= 0; --y)
		{
			for (int x = 0; x < _width; ++x)
			{				
				if (_aPos[0] == x && _aPos[1] == y)
				{
					System.out.print("V  ");
				}
				else
				{
					if (_area[x][y].getPercept().equals("OBSTACLE"))
					{
						System.out.print("X  ");
					}
					else if  (_area[x][y].getPercept().equals("DIRT"))
					{
						System.out.print("D  ");
					}
					else if  (_area[x][y].isHome() && _aPos[0] != x && _aPos[1] != y)
					{
						System.out.print("H  ");
					}
					else
					{
						System.out.print("O  ");
					}
				}
				
			}
			System.out.println();
		}
		
	}
}
